﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;


namespace CodeStash.Addin.Core
{

    using Validation = System.Windows.Controls.Validation;

    public static class ControlValidation
    {
        static ControlValidation()
        {
            ResourceDictionary rd = new ResourceDictionary();
            rd.Source = new Uri(@"/CodeStash.Addin.Core;component/Resources/ErrorTemplates.xaml", UriKind.RelativeOrAbsolute);
            ErrorTemplate = (ControlTemplate)rd["errorTemplate"];
        }

        public static ControlTemplate ErrorTemplate
        {
            get;
            private set;
        }

        private sealed class ValidationChangeMonitor : IWeakEventListener, INotifyPropertyChanged
        {
            private INotifyDataErrorInfo notifyDataErrorInfo;

            public ValidationChangeMonitor(string propertyName, INotifyDataErrorInfo notifyDataErrorInfo)
            {
                this.notifyDataErrorInfo = notifyDataErrorInfo;
                this.PropertyName = propertyName;
                NotifyDataErrorInfoEventManager.AddListener(notifyDataErrorInfo, this);
            }

            bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
            {
                if (managerType == typeof(NotifyDataErrorInfoEventManager))
                {
                    DataErrorsChangedEventArgs dataErrorsChangedEventArgs = (DataErrorsChangedEventArgs)e;

                    if (dataErrorsChangedEventArgs.PropertyName == PropertyName)
                    {
                        Errors = notifyDataErrorInfo.GetErrors(PropertyName);
                    }
                }
                else
                {
                    return false;
                }
                return true;
            }

            private string _propertyName;

            public string PropertyName
            {
                get
                {
                    return _propertyName;
                }
                set
                {
                    if (_propertyName != value)
                    {
                        _propertyName = value;
                        NotifyChanged("PropertyName");
                    }
                }
            }

            private IEnumerable _errors;

            public IEnumerable Errors
            {
                get
                {
                    return _errors;
                }
                private set
                {
                    if (_errors != value)
                    {
                        _errors = value;
                        NotifyChanged("Errors");
                    }
                }
            }

            public void RefreshErrors()
            {
                Errors = notifyDataErrorInfo.GetErrors(PropertyName);
            }

            public void Stop()
            {
                NotifyDataErrorInfoEventManager.RemoveListener(notifyDataErrorInfo, this);
            }

            #region INotifyPropertyChanged Implementation

            /// <summary>
            /// Occurs when any properties are changed on this object.
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;

            private void NotifyChanged(string propertyNames)
            {
                PropertyChangedEventHandler handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyNames));
                }
            }

            #endregion
        }

        private static ValidationChangeMonitor GetValidationChangeMonitor(DependencyObject obj)
        {
            return (ValidationChangeMonitor)obj.GetValue(ValidationChangeMonitorProperty);
        }

        private static void SetValidationChangeMonitor(DependencyObject obj, ValidationChangeMonitor value)
        {
            obj.SetValue(ValidationChangeMonitorProperty, value);
        }

        private static readonly DependencyProperty ValidationChangeMonitorProperty =
            DependencyProperty.RegisterAttached("ValidationChangeMonitor", typeof(ValidationChangeMonitor), typeof(ControlValidation),
            new FrameworkPropertyMetadata(OnValidationSourcePropertyChangedChanged));

        private static void OnValidationSourcePropertyChangedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.OldValue != null)
            {
                ValidationChangeMonitor validationChangeMonitor = (ValidationChangeMonitor)e.NewValue;
                validationChangeMonitor.Stop();
            }

            if (e.NewValue != null)
            {
                //ValidationChangeMonitor validationChangeMonitor = (ValidationChangeMonitor)e.NewValue;
                Binding binding = new Binding("Errors");
                binding.Source = e.NewValue;
                BindingOperations.SetBinding(d, ErrorsProperty, binding);
            }
        }

        public static IEnumerable GetErrors(DependencyObject obj)
        {
            return (IEnumerable)obj.GetValue(ErrorsProperty);
        }

        public static void SetErrors(DependencyObject obj, IEnumerable value)
        {
            obj.SetValue(ErrorsProperty, value);
        }

        // Using a DependencyProperty as the backing store for Errors.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ErrorsProperty =
            DependencyProperty.RegisterAttached("Errors", typeof(IEnumerable), typeof(ControlValidation),
            new FrameworkPropertyMetadata(OnErrorsPropertyChanged));

        private static void OnErrorsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (GetHasErrors(d))
            {
                BindingExpression bindingExpression = BindingOperations.GetBindingExpression(d, ValidationFailedProperty);
                if (bindingExpression != null)
                {
                    IEnumerable errors = (IEnumerable)e.NewValue;
                    if (errors != null)
                    {
                        Validation.MarkInvalid(bindingExpression, new ValidationError(new ControlValidationRule(errors), bindingExpression, errors, null));
                    }
                    else
                    {
                        Validation.ClearInvalid(bindingExpression);
                    }
                }
            }

        }

        public static INotifyDataErrorInfo GetValidationSource(DependencyObject obj)
        {
            return (INotifyDataErrorInfo)obj.GetValue(ValidationSourceProperty);
        }

        public static void SetValidationSource(DependencyObject obj, INotifyDataErrorInfo value)
        {
            obj.SetValue(ValidationSourceProperty, value);
        }

        // Using a DependencyProperty as the backing store for ValidationSource.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValidationSourceProperty =
            DependencyProperty.RegisterAttached("ValidationSource", typeof(INotifyDataErrorInfo), typeof(ControlValidation),
               new FrameworkPropertyMetadata(OnValidationSourcePropertyChanged));

        private static void OnValidationSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.OldValue != null && e.NewValue == null)
            {
                BindingOperations.ClearBinding(d, HasErrorsProperty);
            }
            else
            {
                Binding binding = new Binding("HasErrors");
                binding.Source = e.NewValue;

                SetValidationChangeMonitor(d, new ValidationChangeMonitor(GetErrorProperty(d), (INotifyDataErrorInfo)e.NewValue));

                BindingOperations.SetBinding(d, HasErrorsProperty, binding);
                BindingOperations.SetBinding(d, ValidationFailedProperty, new Binding());
            }
        }



        public static bool GetHasErrors(DependencyObject obj)
        {
            return (bool)obj.GetValue(HasErrorsProperty);
        }

        public static void SetHasErrors(DependencyObject obj, bool value)
        {
            obj.SetValue(HasErrorsProperty, value);
        }

        // Using a DependencyProperty as the backing store for HasErrors.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HasErrorsProperty =
            DependencyProperty.RegisterAttached("HasErrors", typeof(bool), typeof(ControlValidation),
           new FrameworkPropertyMetadata(false, OnHasErrorsPropertyChanged));

        private static void OnHasErrorsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BindingExpression bindingExpression = BindingOperations.GetBindingExpression(d, ValidationFailedProperty);
            if (bindingExpression != null)
            {
                if (GetHasErrors(d))
                {
                    GetValidationChangeMonitor(d).RefreshErrors();
                }
                else
                {
                    Validation.ClearInvalid(bindingExpression);
                }
            }
        }

        public static bool GetValidationFailed(DependencyObject obj)
        {
            return (bool)obj.GetValue(ValidationFailedProperty);
        }

        public static void SetValidationFailed(DependencyObject obj, bool value)
        {
            obj.SetValue(ValidationFailedProperty, value);
        }

        public static readonly DependencyProperty ValidationFailedProperty =
            DependencyProperty.RegisterAttached("ValidationFailed", typeof(bool), typeof(ControlValidation), new UIPropertyMetadata(false));

        public static string GetErrorProperty(DependencyObject obj)
        {
            return (string)obj.GetValue(ErrorPropertyProperty);
        }

        public static void SetErrorProperty(DependencyObject obj, string value)
        {
            obj.SetValue(ErrorPropertyProperty, value);
        }

        public static readonly DependencyProperty ErrorPropertyProperty =
            DependencyProperty.RegisterAttached("ErrorProperty", typeof(string), typeof(ControlValidation),
            new UIPropertyMetadata(string.Empty, OnErrorPropertyPropertyChanged));

        private static void OnErrorPropertyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ValidationChangeMonitor validationChangeMonitor = GetValidationChangeMonitor(d);
            if (validationChangeMonitor != null)
            {
                validationChangeMonitor.PropertyName = (string)e.NewValue;

                if (GetHasErrors(d))
                {
                    validationChangeMonitor.RefreshErrors();
                }
            }
        }


        public class ControlValidationRule : ValidationRule
        {
            private readonly object errorContent;

            public ControlValidationRule(object errorContent)
            {
                this.errorContent = errorContent;
            }

            public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
            {
                return new ValidationResult(false, errorContent);
            }
        }
    }
}